package com.ces.knacky.services.implementor;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;


import com.ces.knacky.framework.exception.DataAccessException;
import com.ces.knacky.framework.services.implementor.CommonServiceImplementor;
import com.ces.knacky.model.OrderItem;
import com.ces.knacky.services.InventoryService;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import com.rabbitmq.client.QueueingConsumer;
/**
 * It is used to interact with remote inventories to check availability of the items in an order.
 * 
 * @author anup_rohamare
 *
 */


public class InventoryServiceImplementor extends CommonServiceImplementor implements InventoryService {
	
	 
	private String username;
	private String password;
	private String virtualHost;
	private String host;
	private int port;
	 
	private String inQueueName;
	private String outQueueName;
	private String exchangeName;
	private String inRouteKey;
	private String outRouteKey;
	 
	boolean durable=true;
	boolean autoDelete=false;
	boolean exclusive=false;
	boolean autoAck=true;
	
	/**
	 * @param - order_id - the id of the selected order.
	 * @return - List of Inventory beans.
	 */
	@Override
	public List<InventoryBean> checkAvailability(int order_id) throws DataAccessException {
		 List<InventoryBean> inventoryList = new ArrayList<InventoryBean> ();
		 ConnectionFactory factory = new ConnectionFactory();
		 factory.setUsername(username);
		 factory.setPassword(password);
		 factory.setVirtualHost(virtualHost);
		 factory.setHost(host);
		 factory.setPort(port);
		 try{
			 Connection connection=factory.newConnection();
			 Channel channel=connection.createChannel();
			 channel.exchangeDeclare(exchangeName,"direct",durable,autoDelete,null);
			 channel.queueDeclare(inQueueName,durable, autoDelete,exclusive,null);
			 channel.queueDeclare(outQueueName,durable, autoDelete,exclusive,null);
			 channel.queueBind(inQueueName, exchangeName, inRouteKey);
			 channel.queueBind(outQueueName, exchangeName, outRouteKey);
			 
			 String str=null;
			 byte[] messageBodyBytes=null;
			 String query = "Select orderItem from OrderItem orderItem where orderItem.order.id="+order_id;
			 List<OrderItem> orderItemList=(List<OrderItem>) list(query);
			 for(OrderItem entity: orderItemList) {
				 OrderItem orderItem = entity;
				 str=orderItem.getItem().getId()+","+orderItem.getQuantity();
				 messageBodyBytes = str.getBytes();
				 channel.basicPublish (exchangeName, inRouteKey,
					MessageProperties.PERSISTENT_TEXT_PLAIN, messageBodyBytes);
			 }
			 QueueingConsumer consumer = new QueueingConsumer(channel);
			 QueueingConsumer.Delivery delivery=null;
			 for(int count=0;count<orderItemList.size();count++){
				 channel.basicConsume(outQueueName, autoAck, consumer);
				 try {
					 delivery = consumer.nextDelivery();
				 } catch (InterruptedException e) {
					 throw new DataAccessException(e);
				 }
				 String deliveryString = new String(delivery.getBody());
				 System.out.println("Response Received: "+deliveryString);
				 inventoryList.add(processMessageReceived(deliveryString));
			 }
			 channel.close();
			 connection.close();
		 }catch(IOException ex){
			 	throw new DataAccessException("Error encoutered in Inventory Service",ex);
		 }
		 return inventoryList;
	 }
	
	/**
	 * @param - order_id - the id of the selected order.
	 * @return - Return whether an order is valid depending upon the availability
	 * 			 of the items in that order. Returns false when one or more items in that 
	 * 			 order are out of stock or if no items exist in that order.
	 */
	@Override
	public boolean isOrderDelayed(int order_id) throws DataAccessException {
		boolean result = false;
		try {
			List<InventoryBean> inventoryBeansList=(List<InventoryBean>) this.checkAvailability(order_id);
			if(inventoryBeansList==null)
				return false;
			result=CollectionUtils.exists(inventoryBeansList,new Predicate(){
			

				@Override
				public boolean evaluate(Object object) {
					int availableQuantity=((InventoryBean)object).getAvailableQuantity();
					int requestedQuantity=((InventoryBean)object).getRequestedQuantity();
					return(((availableQuantity-requestedQuantity)<0));
				}
			
			});
		} catch (DataAccessException e) {
			throw new DataAccessException(e);
		}
		return result; 
	}

	private InventoryBean processMessageReceived(String deliveryString) {
		String []deliveryArray =deliveryString.split(",");
		return new InventoryBean(Integer.parseInt(deliveryArray[0]),Integer.parseInt(deliveryArray[1]),Integer.parseInt(deliveryArray[2])); 
	}

	public String getUsername() {
		return username;
	}



	public void setUsername(String username) {
		this.username = username;
	}



	public String getPassword() {
		return password;
	}



	public void setPassword(String password) {
		this.password = password;
	}



	public String getVirtualHost() {
		return virtualHost;
	}



	public void setVirtualHost(String virtualHost) {
		this.virtualHost = virtualHost;
	}



	public String getHost() {
		return host;
	}



	public void setHost(String host) {
		this.host = host;
	}



	public int getPort() {
		return port;
	}



	public void setPort(int port) {
		this.port = port;
	}



	public String getInQueueName() {
		return inQueueName;
	}



	public void setInQueueName(String inQueueName) {
		this.inQueueName = inQueueName;
	}



	public String getOutQueueName() {
		return outQueueName;
	}



	public void setOutQueueName(String outQueueName) {
		this.outQueueName = outQueueName;
	}



	public String getExchangeName() {
		return exchangeName;
	}



	public void setExchangeName(String exchangeName) {
		this.exchangeName = exchangeName;
	}



	public String getInRouteKey() {
		return inRouteKey;
	}



	public void setInRouteKey(String inRouteKey) {
		this.inRouteKey = inRouteKey;
	}



	public String getOutRouteKey() {
		return outRouteKey;
	}



	public void setOutRouteKey(String outRouteKey) {
		this.outRouteKey = outRouteKey;
	}



	public boolean isDurable() {
		return durable;
	}



	public void setDurable(boolean durable) {
		this.durable = durable;
	}



	public boolean isAutoDelete() {
		return autoDelete;
	}



	public void setAutoDelete(boolean autoDelete) {
		this.autoDelete = autoDelete;
	}



	public boolean isExclusive() {
		return exclusive;
	}



	public void setExclusive(boolean exclusive) {
		this.exclusive = exclusive;
	}
	
	public boolean isAutoAck() {
		return autoAck;
	}



	public void setAutoAck(boolean autoAck) {
		this.autoAck = autoAck;
	}
	
	/**
	 * It is a bean which represents the composite representation of ItemId , 
	 * the quantity requested and quantity available.
	 * It stores the results received from remote inventory which are
	 * further returned so that they can used with Java API.
	 * 
	 * @author anup_rohamare
	 *
	 */
	public class InventoryBean {
		private int itemId;
		
		private int requestedQuantity;
		
		private int availableQuantity;

		public InventoryBean(int itemId, int requestedQuantity) {
			super();
			this.itemId = itemId;
			this.requestedQuantity = requestedQuantity;
		}

		public InventoryBean(int itemId, int requestedQuantity,
				int availableQuantity) {
			super();
			this.itemId = itemId;
			this.requestedQuantity = requestedQuantity;
			this.availableQuantity = availableQuantity;
		}

		public int getItemId() {
			return itemId;
		}

		public void setItemId(int itemId) {
			this.itemId = itemId;
		}

		public int getRequestedQuantity() {
			return requestedQuantity;
		}

		public void setRequestedQuantity(int requestedQuantity) {
			this.requestedQuantity = requestedQuantity;
		}

		public int getAvailableQuantity() {
			return availableQuantity;
		}

		public void setAvailableQuantity(int availableQuantity) {
			this.availableQuantity = availableQuantity;
		}
		
		
		
	}
	
}
